﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace Magiq.Support
{
    public class ExpressionSplit<T, TReturn>
    {
        public Expression<Func<T, IEnumerable<TReturn>>> PropertyExpression { get; private set; }
        public Expression<Func<TReturn, bool>> WhereExpression { get; private set; }

        public static ExpressionSplit<T, TReturn> From(LambdaExpression lambda)
        {
            return ExpressionSplit.From(lambda);
        }

        public static implicit operator ExpressionSplit<T,TReturn>( ExpressionSplit split)
        {
            return new ExpressionSplit<T, TReturn>
            {
                PropertyExpression = (Expression<Func<T, IEnumerable<TReturn>>>)split.PropertyExpression,
                WhereExpression = (Expression<Func<TReturn, bool>>)split.WhereExpression
            };
        }
    }

    public class ExpressionSplit
    {
        private static readonly MethodInfo[] WhereMethods = typeof(Enumerable).GetMethods()
            .Where(x => x.Name == "Where")
            .Union(typeof(Queryable).GetMethods().Where(x => x.Name == "Where"))
            .ToArray();

        public LambdaExpression PropertyExpression { get; private set; }
        public LambdaExpression WhereExpression { get; private set; }

        public Type ReturnType
        {
            get { return WhereExpression == null? PropertyExpression.Body.Type.GetGenericArguments().First() :  WhereExpression.Parameters.Single().Type; }
        }

        public Type Type
        {
            get { return PropertyExpression.Parameters.Single().Type; }
        }

        public static ExpressionSplit From(LambdaExpression lambda)
        {
            var split = new ExpressionSplit();

            var call = lambda.Body as MethodCallExpression;
            Expression propertyBody;
            if (call != null && call.Method.GetGenericMethodDefinition().In(WhereMethods))
            {
                propertyBody = call.Arguments[0];

                var where = call.Arguments[1];
                if (where.NodeType == ExpressionType.Quote)
                    where = ((UnaryExpression)where).Operand;

                split.WhereExpression = (LambdaExpression) where;
            }
            else
            {
                propertyBody = lambda.Body;
            }

            split.PropertyExpression = Expression.Lambda(propertyBody.WrapInEnumerable(), lambda.Parameters);

            return split;
        }
    }
}